using Coravel;
using CRM.Authorization;
using CRM.Authorization.Permissions;
using CRM.Controllers;
using CRM.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Threading.Tasks;
using CRM.Anyleads;
using CRM.Authorization.Rule;
using CRM.Caches;
using CRM.Services;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Caching.Memory;
using VueCliMiddleware;
using EmailMarketing = CRM.Authorization.Permissions.EmailMarketing;
using RoleHandler = CRM.Authorization.Role.Handler;
using RoleRequirement = CRM.Authorization.Role.Requirement;
using RuleHandler = CRM.Authorization.Rule.Handler;
using RuleRequirement = CRM.Authorization.Rule.Requirement;
using CRM.Extensions;
using CRM.HttpProxy;
using CRM.Jobs;
using CRM.Models.GlobalSearch;
using CRM.Services.Email;
using CRM.ViewModels.GlobalSearch;
using Mailjet.Client;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace CRM
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }


        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton(services);
            services.AddLogging();

            #region json序列化

            services.AddControllers().AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
            }).ConfigureApiBehaviorOptions(options =>
            {
                options.InvalidModelStateResponseFactory = context =>
                {
                    var element = context.ModelState.First(m =>
                        m.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid);
                    var result = new BaseController.ResultStruct()
                    {
                        Code = 101,
                        Message = element.Key + element.Value.Errors.First().ErrorMessage
                    };

                    return new JsonResult(result);
                };
            });

            #endregion

            #region 数据库

            var database = Configuration.GetConnectionString("database") ?? "mysql";
            var connectStr = Configuration.GetConnectionString(database);
            // 使用dbContextPool线程池注意
            // 1. 无法触发ChangeTracker
            // 2. OnConfiguring方法不会再次触发
            // 3. 数据库连接不会被释放，可能导致数据库无法连接
            services.AddDbContext<MyDbContext>(r =>
            {
                // Console.WriteLine($"services.AddDbContext(MyDbContext) {database}:{connectStr}");
                _ = database.ToUpper() switch
                {
#if SQLITE
                    "SQLITE" => r.UseSqlite(connectStr),
#endif
#if MYSQL
                    "MYSQL" => r.UseMySql(connectStr, ServerVersion.AutoDetect(connectStr)).EnableSensitiveDataLogging()
                        .EnableDetailedErrors(),
#endif
#if SQLSERVER
                    "SQLSERVER" => r.UseSqlServer(connectStr),
#endif
                    _ => throw new ArgumentException($"Unsupported DatabaseDriver:{database}")
                };
                r.ReplaceService<IMigrationsModelDiffer, MigrationsModelDifferWithoutForeignKey>();
            });

            #endregion


            services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ui"; });

            #region 认证授权

            services.AddIdentity<User, Role>(options =>
                {
                    // Password settings.
                    options.Password = new PasswordOptions
                    {
                        RequiredLength = 5, //要求密码最小长度，默认是 6 个字符
                        RequireDigit = false, //要求有数字
                        RequiredUniqueChars = 0, //要求至少要出现的字母数
                        RequireLowercase = false, //要求小写字母
                        RequireNonAlphanumeric = false, //要求特殊字符
                        RequireUppercase = false //要求大写字母
                    };

                    // Lockout settings.
                    options.Lockout = new LockoutOptions
                    {
                        AllowedForNewUsers = true, // 新用户锁定账户
                        DefaultLockoutTimeSpan = TimeSpan.FromHours(30), //锁定时长，默认是 5 分钟
                        MaxFailedAccessAttempts = 5 //登录错误最大尝试次数，默认 5 次
                    };
                    //options.Stores = new StoreOptions
                    //{
                    //    MaxLengthForKeys = 128, // 主键的最大长度
                    //    ProtectPersonalData = true //保护用户数据，要求实现 IProtectedUserStore 接口
                    //};
                    //options.Tokens = new TokenOptions
                    //{
                    //    AuthenticatorIssuer = "Identity", //认证的消费者
                    //    AuthenticatorTokenProvider = "MyAuthenticatorTokenProvider", //认证令牌的提供者
                    //    ChangeEmailTokenProvider = "MyChangeEmailTokenProvider", //更换邮箱的令牌提供者
                    //    ChangePhoneNumberTokenProvider = "MyChangePhoneNumberTokenProvider", //更换手机号的令牌提供者
                    //    EmailConfirmationTokenProvider = "MyEmailConfirmationTokenProvider", //验证邮箱的令牌提供者
                    //    PasswordResetTokenProvider = "MyPasswordResetTokenProvider", //重置密码的令牌提供者
                    //    ProviderMap = new Dictionary<string, TokenProviderDescriptor>()
                    //};
                    // User settings.
                    options.User = new UserOptions
                    {
                        RequireUniqueEmail = false, //要求Email唯一
                        AllowedUserNameCharacters =
                            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+" //允许的用户名字符，默认是 
                    };

                    options.ClaimsIdentity = new ClaimsIdentityOptions
                    {
                        // RoleClaimType = "IdentityRole",
                        UserIdClaimType = "UserId",
                        // SecurityStampClaimType = "SecurityStamp",
                        // UserNameClaimType = "IdentityName"
                    };
                    //options.SignIn = new SignInOptions
                    //{
                    //    RequireConfirmedEmail = true, //要求激活邮箱
                    //    RequireConfirmedPhoneNumber = true //要求激活手机号
                    //};
                }).AddEntityFrameworkStores<MyDbContext>()
                .AddDefaultTokenProviders(); //重置密码 No IUserTokenProvider named 'Default' is registered 配置信息
            services.ConfigureApplicationCookie(config =>
            {
                config.Cookie.Name = "erp_session";
                config.Cookie.HttpOnly = false;
                config.Events.OnRedirectToLogin = async (o) =>
                {
                    //Console.WriteLine("OnRedirectToLogin");
                    //未找到注入CookieAuthenticationHandler的方法,只好重写重定向到登录页的事件

                    //var i = o.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();//i.ActionContext为空,在非MVC的上下文中无法使用
                    //await (new JsonResult(new BaseController.ResultStruct() { Message = "未授权", Code = 401 }) { StatusCode = 200 }).ExecuteResultAsync(i.ActionContext);

                    //var res = JsonConvert.SerializeObject(new BaseController.ResultStruct() {Code = 401,Message = "未授权"});
                    var res = "{\"message\":\"未授权\",\"data\":null,\"code\":50001}";
                    o.Response.StatusCode = 200;
                    o.Response.ContentType = "application/json;charset=UTF-8";
                    await o.Response.WriteAsync(res);
                };
            });
            //services.AddAuthentication().AddCookie(o => { o.Cookie.Name = "crm_auth"; o.LoginPath = "xxx"; });//AddIdentity 下会自动调用

            //services.AddAuthentication(x =>
            //{
            //    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            //    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            //}).AddJwtBearer(x =>
            //{
            //    // x.RequireHttpsMetadata = true;
            //    x.SaveToken = true;
            //    x.TokenValidationParameters = new TokenValidationParameters
            //    {
            //        ValidateIssuer = false,
            //        // ValidIssuer = jwtTokenConfig.Issuer,
            //        ValidateIssuerSigningKey = true,
            //        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456123456123456")),
            //        // ValidAudience = jwtTokenConfig.Audience,
            //        ValidateAudience = false,
            //        ValidateLifetime = true,
            //        LifetimeValidator = ((_, expires, _, _) => DateTime.UtcNow < expires)
            //        // ClockSkew = TimeSpan.FromMinutes(1)
            //    };
            //});
            services.AddDistributedMemoryCache();
            services.AddSession(o => o.Cookie = new() { Name = "crm_session", HttpOnly = false });
            services.AddSingleton<IAuthorizationHandler, RoleHandler>();
            services.AddSingleton<IAuthorizationHandler, RuleHandler>();
            services.AddAuthorization(options =>
            {
                options.AddPolicy("user",
                    policy => { policy.AddRequirements(new RoleRequirement("admin", "super-admin")); });
                options.AddPolicy(nameof(PermissionAttribute),
                    policy => policy.AddRequirements(new RuleRequirement())
                );
            });

            #endregion

            // services.AddMailer(Configuration);

            #region 任务调度,定时

            services.AddScheduler();
            services.AddQueue();
            services.TryAddTransient<TimingSendEmailJob>();
            services.TryAddTransient<TrackReadEmailJob>();
            services.TryAddTransient<AutoMoveToWatersJob>();
            #region 注入买家数据
            services.TryAddTransient<TimingGetBuyerData>();
            #endregion
            services.AddTransient<Remove1000EmailLog>();
            services.AddSingleton<EmailReadEvent>();


            #endregion

            // services.AddMvc();
            services.AddHttpContextAccessor();
            // BUG: 无法注入
            // services.AddScoped<IActionContextAccessor, ActionContextAccessor>();


            #region 注入services

            var subClasses = Helper.GetSubClasses(typeof(BaseService)).ToList();
            foreach (var subClass in subClasses)
            {
                services.AddPropertyService(subClass);
            }

            #endregion

            #region 注入caches

            services.AddMemoryCache();
            // var cacheSubClasses = Helper.GetSubClasses(typeof(BaseCache<>)).ToList();
            // foreach (var subClass in cacheSubClasses)
            // {
            //     services.TryAddSingleton(subClass);
            //     services.TryAddSingleton(typeof(BaseCache<>),subClass);
            // }
            services.TryAddSingleton(typeof(UserCache));
            services.TryAddSingleton(typeof(NationCache));
            #endregion

            #region 注入邮箱

            var mailjetApiKey = Configuration["Mailjet:apiKey"];
            var mailjetApiSecret = Configuration["Mailjet:apiSecret"];
            if (string.IsNullOrEmpty(mailjetApiKey) || string.IsNullOrEmpty(mailjetApiSecret))
            {
                throw new Exception("缺少邮件配置密钥");
            }

            services.AddHttpClient<IMailjetClient, MailjetClient>(client =>
            {
                //set BaseAddress, MediaType, UserAgent
                client.SetDefaultSettings();
                // client.UseBearerAuthentication("access_token");
                //or
                client.UseBasicAuthentication(mailjetApiKey, mailjetApiSecret);
            });

            #endregion

            

            #region Anyleads Api

            services.AddTransient<MiHttpProxy>();
            services.AddAnyleadsClient(option =>
            {
                option.ApiKey = Configuration["Anyleads:apiKey"];
                option.Domain = Configuration["Anyleads:domain"];
            });


            services.Configure<EmailDogConfig>(Configuration.GetSection("EmailDog"));
            services.Configure<GlobalSearchService.FakeRatioOptions>(Configuration);
            services.TryAddTransient<EmailDogClient>();

            #endregion

            services.AddAutoMapper(config =>
            {
                config.CreateMap<KeywordsDomain, GlobalSearchDto>();
                config.CreateMap<KeywordsDomain, GlobalSearchInfoDto>();
            });
            services.AddRushMail("4376", "8679e15771654cbf5dc28918b9d609b3");
            services.AddSingleton<IOemEmailApplicationFactory, OemEmailApplicationFactory>();
            services.AddHttpClient();
            services.AddHostedService<BackgroundQueue>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            CreateAdmin(app);
            app.UseSession();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }
            if (!env.IsProduction())
            {
                app.Use(((context, func) =>
                {
                    context.Response.Headers.Add("env", env.EnvironmentName);
                    return func();
                }));
            }

            app.UseStaticFiles();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseStatusCodePages((code) =>
            {
                if (code.HttpContext.Response.StatusCode != 401) return Task.Run(() => code);
                var msg = code.HttpContext.Response.StatusCode switch
                {
                    401 => "未授权",
                    _ => throw new ArgumentOutOfRangeException()
                };
                var res = JsonConvert.SerializeObject(new BaseController.ResultStruct()
                {
                    Code = 401,
                    Message = msg
                });

                code.HttpContext.Response.StatusCode = 200;
                code.HttpContext.Response.ContentType = "application/json;charset=UTF-8";
                return code.HttpContext.Response.WriteAsync(res);
            });

            app.ApplicationServices.UseScheduler(scheduler =>
            {
                // 后期需要改善 将时间单独拉出来到队列或者单独表，缓解MarketingTask表压力
                scheduler.Schedule<TimingSendEmailJob>().EveryMinute();
                scheduler.Schedule<TrackReadEmailJob>().EveryMinute();
                // scheduler.Schedule<AutoMoveToWatersJob>().EveryMinute();
                // scheduler.Schedule<TimingSendEmailJob>().EverySecond();               

                //买家数据定时任务
                scheduler.Schedule<TimingGetBuyerData>().Hourly();
                scheduler.Schedule<Remove1000EmailLog>().EveryMinute();
                /*
                  
                var SHM =new System.Threading.AutoResetEvent(true);
                var Sp = new System.Threading.Semaphore(0,1);
                if(TimingGetBuyerData.RecordCount > 0 ){
                    SHM.Set();//放行
                    Sp.Release();//-1
                }
                ImageTask{
                    do{
                        SHM.WaitOne();// --开关
                        Sp.WaitOne();//+1
                        do content.buyerdata.where(imagedownload = false){
                            download(image)
                        }
                        SHM.Reset();//禁止
                    }
               }
               */
            }).OnError(e =>
            {
                Console.WriteLine(e.ToString());
            });

            app.UseRouting();
            //授权过程 自定义（role）policy授权 https://www.cnblogs.com/leon-ytparty/articles/9444421.html权限校验
            app.UseAuthentication(); //认证过程 发生在 Mvc中间件中
            app.UseAuthorization(); //授权过程 发生在 Authentication 中间件中
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                // endpoints.MapControllerRoute(
                //     name: "default",
                //     pattern: "{controller}/{action=Index}/{id?}"
                // );
            });

            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = Configuration["Spa:SourcePath"];

                // if (!env.IsProduction())
                // {
                //     var mode = Configuration["Spa:mode"];
                //     var port = Configuration["Spa:port"];
                //
                //     // run npm process with client app
                //     if (mode == "start")
                //     {
                //         spa.UseVueCli(npmScript: "serve", port: Convert.ToInt32(port), forceKill: true);
                //     }
                //
                //     // if you just prefer to proxy requests from client app, use proxy to SPA dev server instead,
                //     // app should be already running before starting a .NET client:
                //     //// run npm process with client app
                //     if (mode == "attach")
                //     {
                //         spa.UseProxyToSpaDevelopmentServer($"http://localhost:{port}"); // your Vue app port
                //     }
                // }
            });
        }

        private static void CreateAdmin(IApplicationBuilder app)
        {
            using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
            {
                //Migrate any pending changes: 
                var myDbContext = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();
                // myDbContext.Database.Migrate();
                var MemoryCache = serviceScope.ServiceProvider.GetRequiredService<IMemoryCache>();
                //Issue BuyersData Initial
                //if(myDbContext.BuyersData.Any()){
                //     //async
                //     //1000
                //     //    page 10 Issue offset
                //     //    1000 30 outtime
                //     //scheduler.Schedule<TimingGetBuyerData>().Hourly();
                //}else{
                //     //TimingGetBuyerData.Now() "xxxx index"
                //     //scheduler.Schedule<TimingGetBuyerData>().Hourly()
                //}
                if (myDbContext.Users.Any()) return;
                var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<User>>();
                var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
                /* var DataSources = serviceScope.ServiceProvider.GetRequiredService<DataSources>(); */

                //首次创建公司
                Role r1 = new()
                { Id = 1, Name = "su", DisplayName = "超级管理员", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now, };
                Role r2 = new()
                {
                    Id = 2,
                    Name = "admin",
                    DisplayName = "系统管理员",
                    CreatedAt = DateTime.Now,
                    UpdatedAt = DateTime.Now,
                };
                Role r3 = new()
                {
                    Id = 3,
                    Name = "company",
                    DisplayName = "公司管理员",
                    CreatedAt = DateTime.Now,
                    UpdatedAt = DateTime.Now,
                };
                Role r4 = new()
                {
                    Id = 4,
                    Name = "employee",
                    DisplayName = "员工",
                    CreatedAt = DateTime.Now,
                    UpdatedAt = DateTime.Now,
                };
                Role[] rs = { r1, r2, r3, r4 };
                if (rs.All(r => roleManager.CreateAsync(r).Result.Succeeded))
                {
                    //首次创建用户
                    User u1 = new()
                    {
                        UserName = "su",
                        Avatar = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
                        CreatedAt = DateTime.Now,
                        UpdatedAt = DateTime.Now,
                        Email = "su@crm.com",
                        TrueName = "su",
                        RuleId = 1,
                        CompanyId = 1,
                        OemId = 1,
                        FirstLoginExe = DateTime.Now,
                        FirstLoginWeb = DateTime.Now,
                        LoginTime = DateTime.Now,
                        Mobile = string.Empty,
                        Pids = string.Empty,
                        Password = "123456"
                    };
                    User u2 = new User()
                    {
                        UserName = "admin",
                        Avatar = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
                        CreatedAt = DateTime.Now,
                        UpdatedAt = DateTime.Now,
                        Email = "admin@crm.com",
                        TrueName = "admin",
                        RuleId = 1,
                        CompanyId = 1,
                        OemId = 1,
                        FirstLoginExe = DateTime.Now,
                        FirstLoginWeb = DateTime.Now,
                        LoginTime = DateTime.Now,
                        Mobile = string.Empty,
                        Pids = string.Empty,
                        Password = "123456"
                    };
                    User[] us = { u1, u2 };
                    if (us.All(u => userManager.CreateAsync(u, u.Password).Result.Succeeded))
                    {
                        var u = u2;
                        _ = userManager.AddToRoleAsync(u1, r1.Name).Result;
                        _ = userManager.AddToRoleAsync(u2, r2.Name).Result;
                        var cuf = new Claim(nameof(UserManager)) { OemId = u.OemId };
                        var csf = new Claim(nameof(SocialMarketingTasks)) { OemId = u.OemId };
                        var ccf = new Claim(nameof(CustomerManager)) { OemId = u.OemId };
                        var cdf = new Claim(nameof(DataSearch)) { OemId = u.OemId };
                        var cef = new Claim(nameof(EmailMarketing)) { OemId = u.OemId };
                        var cbf = new Claim(nameof(BuyersData)) { OemId = u.OemId };
                        var csd = new Claim(nameof(customsData)) { OemId = u.OemId };
                        var clf = new Claim(nameof(LoggingInfo)) { OemId = u.OemId };
                        var csfNoOEM = new Claim(nameof(SystemManager), SystemManager.OEM, ClaimModes.FULLSUB)
                        { OemId = u.OemId };
                        var csfOEM = new Claim(nameof(SystemManager)) { OemId = u.OemId };

                        //超管 su权限 通过Role[全局仅有su的r1角色是所有权限声明在Role中]
                        Claim[] clms_su_role = { csfOEM, cuf, cdf, cef, cbf, csf, ccf, csd, clf };
                        clms_su_role.All(c => roleManager.AddClaimAsync(r1, c).Result.Succeeded);

                        //非超管 Admin权限 直接给用户 一个特殊的公司账户 以防有平台配置项需要业务人员配置
                        Claim[] clms_admin = { csfNoOEM, cuf, cdf, cef, cbf, csf, ccf, csd, clf };
                        _ = userManager.AddClaimsAsync(u, clms_admin).Result;

                        _ = roleManager.AddClaimAsync(r2, csfNoOEM).Result;
                        _ = roleManager.AddClaimAsync(r3, csfNoOEM).Result;
                    }
                }
            }
        }
    }
}